package org.luxor.commons.workflow.config;


import org.activiti.engine.*;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.impl.persistence.StrongUuidGenerator;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.impl.persistence.entity.integration.IntegrationContextManager;
import org.activiti.engine.integration.IntegrationContextService;
import org.activiti.spring.ProcessEngineFactoryBean;
import org.activiti.spring.SpringAsyncExecutor;
import org.activiti.spring.SpringCallerRunsRejectedJobsHandler;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.luxor.commons.core.config.properties.ThreadPoolProperties;
import org.luxor.commons.core.utils.AssertUtils;
import org.luxor.commons.jdbc.plugin.dynamic.provider.DynamicDataSourceProvider;
import org.luxor.commons.workflow.component.ProcessDefinitionResourceFinder;
import org.luxor.commons.workflow.component.cache.CustomDeploymentCache;
import org.luxor.commons.workflow.component.cache.KryoRedisSerializer;
import org.luxor.commons.workflow.component.cache.MemoryDeploymentCache;
import org.luxor.commons.workflow.component.cache.RedisDeploymentCache;
import org.luxor.commons.workflow.component.mapper.CustomExecutionMapper;
import org.luxor.commons.workflow.component.mapper.CustomHistoricActivityInstanceMapper;
import org.luxor.commons.workflow.component.mapper.CustomHistoricTaskInstanceMapper;
import org.luxor.commons.workflow.component.mapper.CustomTaskMapper;
import org.luxor.commons.workflow.config.properties.WorkflowProperties;
import org.luxor.commons.workflow.exception.RedisDeploymentCacheException;
import org.luxor.commons.workflow.listener.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.*;

/**
 * 开启工作流全局配置
 *
 * @author Mr.Yan  @date 2021/10/11
 */
@Configuration
@AutoConfigureAfter(name = {"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration",
        "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration"})
@EnableConfigurationProperties(WorkflowProperties.class)
public class ProcessEngineAutoConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(ProcessEngineAutoConfiguration.class);
    private static final String ASYNC_THREAD_NAME_PREFIX = "WorkflowAsyncTask-";

    @Bean
    @ConditionalOnMissingBean
    public SpringAsyncExecutor springAsyncExecutor(WorkflowProperties workflowProperties) {
        ThreadPoolProperties threadPoolProperties = workflowProperties.getThreadPool();
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
        executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
        executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
        executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
        executor.setWaitForTasksToCompleteOnShutdown(threadPoolProperties.isWaitForJobsToCompleteOnShutdown());
        executor.setAwaitTerminationSeconds(threadPoolProperties.getAwaitTerminationSeconds());
        executor.setThreadNamePrefix(ASYNC_THREAD_NAME_PREFIX);
        executor.setRejectedExecutionHandler(threadPoolProperties.getRejectedExecutionHandler());
        executor.initialize();
        return new SpringAsyncExecutor(executor, new SpringCallerRunsRejectedJobsHandler());
    }

    /**
     * 数据源
     *
     * @param dynamicDataSourceProvider 动态数据源提供方
     * @param workflowProperties 工作流配置
     * @return javax.sql.DataSource
     */
    @Bean("workflowDataSource")
    public DataSource workflowDataSource(ObjectProvider<DynamicDataSourceProvider> dynamicDataSourceProvider,
                                         WorkflowProperties workflowProperties) {
        logger.info("Workflow DataSource is set to [{}], Started...", workflowProperties.getDataSource());
        try {
            Map<String, DataSource> dataSources = dynamicDataSourceProvider.getObject().loadDataSources();
            DataSource quartzDataSource = dataSources.get(workflowProperties.getDataSource());
            AssertUtils.notNull(quartzDataSource, "There's no [" + workflowProperties.getDataSource() + "] in DynamicDataSources");
            logger.info("Workflow DataSource is set to [{}], completed.", workflowProperties.getDataSource());
            return quartzDataSource;
        } catch (Exception e) {
            logger.error("Workflow DataSource is set to  [" + workflowProperties.getDataSource() + "], Failure.", e);
            throw e;
        }
    }

    /**
     * 流程定义缓存
     */
    @Bean
    @ConditionalOnMissingBean
    public CustomDeploymentCache<ProcessDefinitionCacheEntry> deploymentCache(
            WorkflowProperties workflowProperties,
            ObjectProvider<RedisConnectionFactory> connectionFactory) {
        if (workflowProperties.isRedisCacheEnable()) {
            logger.info("Workflow RedisDeploymentCache initial Started...");
            try {
                RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
                redisTemplate.setDefaultSerializer(new KryoRedisSerializer());
                redisTemplate.setKeySerializer(new StringRedisSerializer());
                redisTemplate.setConnectionFactory(connectionFactory.getObject());
                redisTemplate.afterPropertiesSet();
                redisTemplate.hasKey(RedisDeploymentCache.PROCESS_DEFINITION_NAME);
                CustomDeploymentCache<ProcessDefinitionCacheEntry> deploymentCache = new RedisDeploymentCache<>(redisTemplate);
                logger.info("Workflow RedisDeploymentCache initial Completed");
                return deploymentCache;
            } catch (Throwable e) {
                logger.info("Workflow RedisDeploymentCache initial fail.", e);
                throw new RedisDeploymentCacheException("Workflow RedisDeploymentCache initial fail. ", e);
            }
        } else {
            logger.info("Workflow DefaultDeploymentCache initial Started...");
            CustomDeploymentCache<ProcessDefinitionCacheEntry> deploymentCache = new MemoryDeploymentCache<>();
            logger.info("Workflow DefaultDeploymentCache initial Completed.");
            return deploymentCache;
        }
    }

    /**
     * 全局监听配置
     */
    @Bean
    public GlobalEventListener globaleventlistener(CustomDeploymentCache<ProcessDefinitionCacheEntry> deploymentCache) {
        Map<ActivitiEventType, ActivitiEventHandler> handlers = new HashMap<>(10);
        handlers.put(ActivitiEventType.PROCESS_STARTED, new ProcessStartedListener());
        handlers.put(ActivitiEventType.ACTIVITY_STARTED, new ActivityStartedListener());
        handlers.put(ActivitiEventType.TASK_CREATED, new TaskCreatedListener());
        handlers.put(ActivitiEventType.TASK_ASSIGNED, new TaskAssignedListener());
        handlers.put(ActivitiEventType.TASK_COMPLETED, new TaskCompletedListener());
        handlers.put(ActivitiEventType.ACTIVITY_COMPLETED, new ActivityCompletedListener());
        handlers.put(ActivitiEventType.PROCESS_COMPLETED, new ProcessCompletedListener(deploymentCache));
        handlers.put(ActivitiEventType.PROCESS_COMPLETED_WITH_ERROR_END_EVENT, new ProcessWithErrorEndListener(deploymentCache));
        handlers.put(ActivitiEventType.PROCESS_CANCELLED, new ProcessCancelledListener(deploymentCache));
        return new GlobalEventListener(handlers);
    }

    /**
     * 全局配置类
     *
     * @param workflowDataSource  持久化数据源
     * @param transactionManager  全局事务管理器
     * @param springAsyncExecutor 异步线程池
     * @param workflowProperties  工作流配置参数
     * @return org.activiti.spring.SpringProcessEngineConfiguration
     */
    @Bean
    @ConditionalOnMissingBean
    public SpringProcessEngineConfiguration springProcessEngineConfiguration(
            @Qualifier("workflowDataSource") DataSource workflowDataSource,
            ObjectProvider<PlatformTransactionManager> transactionManager,
            WorkflowProperties workflowProperties,
            ProcessDefinitionResourceFinder processDefinitionResourceFinder,
            SpringAsyncExecutor springAsyncExecutor,
            CustomDeploymentCache<ProcessDefinitionCacheEntry> deploymentCache,
            GlobalEventListener globaleventlistener) throws IOException {
        logger.info("Workflow ProcessEngineConfig Started...");
        logger.info("Workflow ProcessEngineConfig by dataSource:{}", workflowDataSource);

        SpringProcessEngineConfiguration conf = new SpringProcessEngineConfiguration();
        configureProcessDefinitionResources(processDefinitionResourceFinder, conf);
        conf.setDataSource(workflowDataSource);
        conf.setTransactionManager(transactionManager.getIfAvailable());
        conf.setAsyncExecutor(springAsyncExecutor);
        conf.setDeploymentName(workflowProperties.getDeploymentName());
        conf.setDatabaseSchema(workflowProperties.getDatabaseSchema());
        conf.setDatabaseSchemaUpdate(workflowProperties.getDatabaseSchemaUpdate());
        conf.setDbHistoryUsed(workflowProperties.isDbHistoryUsed());
        conf.setAsyncExecutorActivate(workflowProperties.isAsyncExecutorActivate());
        conf.setHistoryLevel(workflowProperties.getHistoryLevel());
        conf.setCopyVariablesToLocalForTasks(workflowProperties.isCopyVariablesToLocalForTasks());
        conf.setIdGenerator(new StrongUuidGenerator());
        conf.setSerializePOJOsInVariablesToJson(workflowProperties.isSerializePOJOsInVariablesToJson());
        conf.setJavaClassFieldForJackson(workflowProperties.getJavaClassFieldForJackson());
        if (workflowProperties.getDeploymentMode() != null) {
            conf.setDeploymentMode(workflowProperties.getDeploymentMode());
        }
        conf.setJavaClassFieldForJackson(workflowProperties.getJavaClassFieldForJackson());
        // 设置流程定义缓存
        conf.setProcessDefinitionCache(deploymentCache);
        // 全局监听器注册
        List<ActivitiEventListener> eventListeners = new ArrayList<>();
        eventListeners.add(globaleventlistener);
        conf.setEventListeners(eventListeners);
        // 自定义SQL
        Set<Class<?>> customMybatisMappers = new HashSet<>();
        customMybatisMappers.add(CustomExecutionMapper.class);
        customMybatisMappers.add(CustomHistoricActivityInstanceMapper.class);
        customMybatisMappers.add(CustomHistoricTaskInstanceMapper.class);
        customMybatisMappers.add(CustomTaskMapper.class);
        conf.setCustomMybatisMappers(customMybatisMappers);
        Set<String> customMybatisXMLMappers = new HashSet<>();
        customMybatisXMLMappers.add("org/luxor/commons/workflow/component/mapper/CustomExecutionMapper.xml");
        customMybatisXMLMappers.add("org/luxor/commons/workflow/component/mapper/CustomHistoricActivityInstanceMapper.xml");
        customMybatisXMLMappers.add("org/luxor/commons/workflow/component/mapper/CustomHistoricTaskInstanceMapper.xml");
        customMybatisXMLMappers.add("org/luxor/commons/workflow/component/mapper/CustomTaskMapper.xml");
        conf.setCustomMybatisXMLMappers(customMybatisXMLMappers);

        logger.info("Workflow ProcessEngineConfig completed.");
        return conf;
    }

    private void configureProcessDefinitionResources(ProcessDefinitionResourceFinder processDefinitionResourceFinder,
                                                     SpringProcessEngineConfiguration conf) throws IOException {
        List<Resource> procDefResources = processDefinitionResourceFinder.discoverProcessDefinitionResources();
        if (!procDefResources.isEmpty()) {
            conf.setDeploymentResources(procDefResources.toArray(new Resource[0]));
        }
    }

    /**
     * 工作流引擎
     *
     * @param configuration 流程引擎配置
     * @return org.activiti.spring.ProcessEngineFactoryBean
     */
    @Bean
    public ProcessEngineFactoryBean processEngine(SpringProcessEngineConfiguration configuration) {
        ProcessEngineFactoryBean processEngineFactoryBean = new ProcessEngineFactoryBean();
        processEngineFactoryBean.setProcessEngineConfiguration(configuration);
        return processEngineFactoryBean;
    }

    @Bean
    @ConditionalOnMissingBean
    public RuntimeService runtimeServiceBean(ProcessEngine processEngine) {
        return processEngine.getRuntimeService();
    }

    @Bean
    @ConditionalOnMissingBean
    public RepositoryService repositoryServiceBean(ProcessEngine processEngine) {
        return processEngine.getRepositoryService();
    }

    @Bean
    @ConditionalOnMissingBean
    public TaskService taskServiceBean(ProcessEngine processEngine) {
        return processEngine.getTaskService();
    }

    @Bean
    @ConditionalOnMissingBean
    public HistoryService historyServiceBean(ProcessEngine processEngine) {
        return processEngine.getHistoryService();
    }

    @Bean
    @ConditionalOnMissingBean
    public ManagementService managementServiceBeanBean(ProcessEngine processEngine) {
        return processEngine.getManagementService();
    }

    @Bean
    @ConditionalOnMissingBean
    public IntegrationContextManager integrationContextManagerBean(ProcessEngine processEngine) {
        return processEngine.getProcessEngineConfiguration().getIntegrationContextManager();
    }

    @Bean
    @ConditionalOnMissingBean
    public IntegrationContextService integrationContextServiceBean(ProcessEngine processEngine) {
        return processEngine.getProcessEngineConfiguration().getIntegrationContextService();
    }

    @Bean
    @Primary
    public ProcessDefinitionResourceFinder processDefinitionResourceFinder(
            WorkflowProperties workflowProperties, ResourcePatternResolver resourcePatternResolver) {
        return new ProcessDefinitionResourceFinder(workflowProperties, resourcePatternResolver);
    }

}
